using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Linq;

namespace RadaCode.SwissKnife.Search
{
    public class XmlImageCacher : IImageCacher
    {
        private readonly FileStream _cacheFileStream;

        private const string cachedImageRecord = "cachedImage";
        private const string cachedImageRoot = "bingImagesCache";

        public XmlImageCacher(FileStream cacheFileStream)
        {
            _cacheFileStream = cacheFileStream;
        }

        #region IDataCacher Methods

        #region Locking

        private XDocument GetCacheSnapshot()
        {
            XDocument xDoc;

            lock (this)
            {
                try
                {
                    _cacheFileStream.Position = 0;
                    xDoc = XDocument.Load(_cacheFileStream);
                }

                catch (XmlException)
                {
                    var root = new XElement(cachedImageRoot);

                    var xdoc = new XDocument();

                    xdoc.Add(root);

                    _cacheFileStream.SetLength(0);
                    xdoc.Save(_cacheFileStream);

                    xDoc = xdoc;
                }

                return xDoc;
            }
        }

        private void SaveCacheSnapshot(XDocument xdoc)
        {
            lock (this)
            {
                _cacheFileStream.SetLength(0);
                xdoc.Save(_cacheFileStream);
            }
        }

        #endregion

        public string GetImageByKeyword(string keyword)
        {
            Debug.WriteLine(String.Format("One {0} image is requested from cache.", keyword));

            var xDoc = GetCacheSnapshot();
            string res = String.Empty;

            var cachedImageNode = xDoc.Descendants(cachedImageRecord).Where(rec => rec.Element("Request").Value == keyword).FirstOrDefault();

            if (cachedImageNode != null)
            {
                res = cachedImageNode.Element("Image").Value;

                cachedImageNode.Remove();
                SaveCacheSnapshot(xDoc);
            }
            
            return res;
        }

        public string[] GetImagesByKeyword(int imageCount, string keyword)
        {
            Debug.WriteLine(String.Format("{0} {1} images are requested from cache.", imageCount, keyword));

            var res = new string[imageCount];

            var numberOfRandomImagesInCache = GetCachedImageCountByKeyword(keyword);

            if (numberOfRandomImagesInCache >= imageCount)
            {
                Debug.WriteLine(String.Format("{0} {1} images are provided from cache.", imageCount, keyword));

                var xDoc = GetCacheSnapshot();
                var imagesNodes =
                    xDoc.Descendants(cachedImageRecord).Where(rec => rec.Element("Request").Value == keyword).Take(
                        imageCount);

                var i = 0;

                foreach (var imagesNode in imagesNodes)
                {
                    res[i] = imagesNode.Element("Image").Value;
                    i++;
                }

                imagesNodes.Remove();

                SaveCacheSnapshot(xDoc);
            }
            else
            {
                res = new string[0];
            }

            return res;
        }

        public string GetRandomImage()
        {
            Debug.WriteLine("A random image is requested from cache.");

            var xDoc = GetCacheSnapshot();
            string res = String.Empty;

            var cachedImageNode = xDoc.Descendants(cachedImageRecord).Where(rec => rec.Element("Request").Value == "Random").FirstOrDefault();

            if (cachedImageNode != null)
            {
                Debug.WriteLine("A random image is provided from cache.");

                res = cachedImageNode.Element("Image").Value;

                cachedImageNode.Remove();
                SaveCacheSnapshot(xDoc);
            }
            
            return res;
        }

        public string[] GetRandomImages(int imageCount)
        {
            Debug.WriteLine(String.Format("{0} random images are requested from cache.", imageCount));

            var res = new string[imageCount];

            var numberOfRandomImagesInCache = GetNumberOfRandomImagesInCache();

            if (numberOfRandomImagesInCache >= imageCount)
            {
                Debug.WriteLine(String.Format("{0} random images are provided from cache.", imageCount));

                var xDoc = GetCacheSnapshot();
                var imagesNodes =
                    xDoc.Descendants(cachedImageRecord).Where(rec => rec.Element("Request").Value == "Random").Take(
                        imageCount);

                var i = 0;

                foreach (var imagesNode in imagesNodes)
                {
                    res[i] = imagesNode.Element("Image").Value;
                    i++;
                }

                imagesNodes.Remove();

                SaveCacheSnapshot(xDoc);
            }
            else
            {
                res = new string[0];
            }

            return res;
        }

        public void AddImagesAsKeyword(IEnumerable<string> images, string keyword)
        {
            Debug.WriteLine(String.Format("Adding {0} {1} images to cache.", images.Count(), keyword));

            var xDoc = GetCacheSnapshot();

            foreach (var image in images)
            {
                xDoc.Root.Add(new XElement(cachedImageRecord,
                                      new XElement("Request", keyword),
                                      new XElement("Image", image)
                             ));
            }

            SaveCacheSnapshot(xDoc);
        }

        public void AddRandomImages(IEnumerable<string> randomImages)
        {
            Debug.WriteLine(String.Format("Adding {0} random images to cache.", randomImages.Count()));

            var xDoc = GetCacheSnapshot();

            foreach (var image in randomImages)
            {
                xDoc.Root.Add(new XElement(cachedImageRecord,
                                      new XElement("Request", "Random"),
                                      new XElement("Image", image)
                             ));
            }

            SaveCacheSnapshot(xDoc);
        }

        public int GetNumberOfRandomImagesInCache()
        {
            var xDoc = GetCacheSnapshot();

            var res = xDoc.Descendants(cachedImageRecord).Where(d => d.Element("Request").Value == "Random").Count();

            Debug.WriteLine(String.Format("There are {0} random images in cache.", res));

            return res;
        }

        public int GetCachedImageCountByKeyword(string keyword)
        {
            var xDoc = GetCacheSnapshot();

            var res = xDoc.Descendants(cachedImageRecord).Where(d => d.Element("Request").Value == keyword).Count();

            Debug.WriteLine(String.Format("There are {0} {1} images in cache.", res, keyword));

            return res;
        }

        #endregion
    }
}